﻿using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;

namespace RMS.Common
{
    /// <summary>
    /// MongoDB操作帮助类（不要用异步方法，关不上链接）
    /// </summary>
    public static class MongoDBHelper
    {
        static NLogHelper nlog = new NLogHelper();
        #region 参数
        /// <summary>
        /// MongoDB客户端，在帮助类实例化的时候进行初始化
        /// </summary>
        public static MongoClient Client;
        /// <summary>
        /// MongoDB操作数据库
        /// </summary>
        public static IMongoDatabase Database;
        #endregion

        #region 构造函数
        /// <summary>
        /// 创建MongoDB帮助类，根据config文件的MongoDBUrl节点进行链接
        /// </summary>
        static MongoDBHelper()
        {
            try
            {
                string databaseName = "SXeDTLog";
                #region 配置类配置方式(参数较多，不推荐)
                //List<MongoServerAddress> seedList = new List<MongoServerAddress>();
                //seedList.Add(new MongoServerAddress("dds-2ze07f469e5917641.mongodb.rds.aliyuncs.com", 3717));
                //seedList.Add(new MongoServerAddress("dds-2ze07f469e5917642.mongodb.rds.aliyuncs.com", 3717));
                //// 构建鉴权信息
                //List<MongoCredential> credentials = new List<MongoCredential>();
                //credentials.Add(MongoCredential.CreateCredential("admin", "root", "Hewenlong123"));
                //// 构建操作选项，requiredReplicaSetName属性外的选项根据自己的实际需求配置，默认参数满足大多数场景
                //MongoClientSettings setting = new MongoClientSettings()
                //{
                //    Servers = seedList,
                //    Credentials = credentials,
                //    ReplicaSetName = "mgset-2997739",
                //    SocketTimeout = new TimeSpan(2000)
                //};
                //Client = new MongoClient(setting); 
                #endregion

                //通过URI初始化
                //mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
                string url = ConfigurationManager.AppSettings["MongoDBUrl"];
                //string url = "mongodb://root:Hewenlong123@47.93.38.132:3717/admin";
                //string url = "mongodb://root:Hewenlong123@dds-2ze07f469e5917641.mongodb.rds.aliyuncs.com:3717,dds-2ze07f469e5917642.mongodb.rds.aliyuncs.com:3717/admin?replicaSet=mgset-2997739";
                MongoUrl connectionString = new MongoUrl(url);
                Client = new MongoClient(connectionString);
                Database = Client.GetDatabase(databaseName);
            }
            catch (Exception e)
            {
                nlog.Fatal(e);
                throw e;
            }
        }
        ///// <summary>
        ///// 创建MongoDB帮助类，根据config文件的MongoDBUrl节点进行链接,默认数据库名称为log
        ///// </summary>
        //public static MongoDBHelper()
        //    : this("log")
        //{
        //}
        #endregion

        #region 私有方法
        /// <summary>
        /// 根据泛型类型获取Collection
        /// </summary>
        /// <typeparam name="T">记录类型</typeparam>
        /// <returns></returns>
        private static IMongoCollection<T> getCollection<T>()
            where T : MongoDBBaseModel
        {
            var result = Database.GetCollection<T>(typeof(T).Name);
            return result;
        }

        /// <summary>
        /// 处理错误日志(错误日志取错误信息，不是错误日志的序列化json字符串)
        /// </summary>
        /// <typeparam name="T">文档类型</typeparam>
        /// <param name="document">文档实例</param>
        /// <returns></returns>
        private static string GetError<T>(T document)
            where T : MongoDBBaseModel
        {
            JObject jo = new JObject();
            Type t = typeof(T);
            System.Reflection.PropertyInfo[] t_propinfos = t.GetProperties();
            for (int i = 0; i < t_propinfos.Length; i++)
            {
                var v = t_propinfos[i].GetValue(document);
                jo.Add(t_propinfos[i].Name, (v == null ? string.Empty : v.ToString()));
            }
            var result = jo.ToString();
            return result;
        }
        #endregion

        #region 添加
        /// <summary>
        /// 添加
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="document">记录实体</param>
        public static void InsertOne<T>(T document)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                collection.InsertOne(document);
            }
            catch (Exception e)
            {
                string data = GetError<T>(document);
                nlog.Error("InsertOne" + ":\r\n" + data);
                nlog.Fatal(e);
                throw e;
            }
        }
        /// <summary>
        /// 批量添加
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="documents">记录实体集合</param>
        public static void InsertMany<T>(IEnumerable<T> documents)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                collection.InsertMany(documents);
            }
            catch (Exception e)
            {
                string data = Newtonsoft.Json.JsonConvert.SerializeObject(documents);
                nlog.Error("InsertMany" + ":\r\n" + data);
                nlog.Fatal(e);
                throw e;
            }
        }
        #endregion

        #region 修改
        /// <summary>
        /// 修改
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="id">mongodb的ObjectId</param>
        /// <param name="data">改后的模型实体</param>
        /// <returns></returns>
        public static ReplaceOneResult Update<T>(T data)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", data._id);
                var result = collection.ReplaceOne(filter, data);
                return result;
            }
            catch (Exception e)
            {
                string datastr = GetError<T>(data);
                nlog.Error("Update" + ":\r\n" + datastr);
                nlog.Fatal(e);
                throw e;
            }
        }
        /// <summary>
        /// 修改
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="id">mongodb的ObjectId</param>
        /// <param name="data">改后的模型实体</param>
        /// <returns></returns>
        public static ReplaceOneResult Update<T>(ObjectId id, T data)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", id);
                var result = collection.ReplaceOne(filter, data);
                return result;
            }
            catch (Exception e)
            {
                string datastr = GetError<T>(data);
                nlog.Error("Update" + ":\r\nid:" + id + "\r\n" + datastr);
                nlog.Fatal(e);
                throw e;
            }
        }
        /// <summary>
        /// 修改
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="id">mongodb的ObjectId</param>
        /// <param name="updatedic">修改信息字典</param>
        /// <returns></returns>
        public static UpdateResult Update<T>(ObjectId id, Dictionary<string, object> updatedic)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", id);
                if (updatedic.Count <= 0)
                {
                    return null;
                }
                UpdateDefinition<T> ud = null;
                foreach (var item in updatedic)
                {
                    if (ud == null)
                    {
                        ud = Builders<T>.Update.Set(item.Key, item.Value);
                    }
                    else
                    {
                        ud.Set(item.Key, item.Value);
                    }
                }
                var result = collection.UpdateOne(filter, ud);
                return result;
            }
            catch (Exception e)
            {
                string data = Newtonsoft.Json.JsonConvert.SerializeObject(updatedic);
                nlog.Error("Update" + ":\r\nid:" + id + "\r\n" + data);
                nlog.Fatal(e);
                throw e;
            }
        }
        /// <summary>
        /// 修改
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="filter">查询用的lambda表达式</param>
        /// <param name="updated">UpdateDefinitionBuilder里的修改方法，可以赋值，可以累加等多种操作</param>
        /// <returns></returns>
        public static UpdateResult Update<T>(Expression<Func<T, bool>> filter, UpdateDefinition<T> updated)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                var result = collection.UpdateMany(filter, updated);
                return result;
            }
            catch (Exception e)
            {
                string data = Newtonsoft.Json.JsonConvert.SerializeObject(updated);
                nlog.Error("Update" + ":\r\nfilter:" + filter.ToString() + "\r\n" + data);
                nlog.Fatal(e);
                throw e;
            }
        }
        #endregion

        #region 删除
        /// <summary>
        /// 删除
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="filter">查询用的lambda表达式</param>
        /// <returns></returns>
        public static DeleteResult Delete<T>(Expression<Func<T, bool>> filter)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                var resut = collection.DeleteMany(filter);
                return resut;
            }
            catch (Exception e)
            {
                nlog.Error("Delete" + ":" + filter.ToString());
                nlog.Fatal(e);
                throw e;
            }
        }
        #endregion

        #region 查询
        /// <summary>
        /// 查询数据条数
        /// </summary>
        /// <typeparam name="T">记录格式类型</typeparam>
        /// <param name="filter">查询用的lambda表达式</param>
        /// <returns></returns>
        public static long Count<T>(Expression<Func<T, bool>> filter)
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                var resut = collection.Count<T>(filter);
                return resut;
            }
            catch (Exception e)
            {
                nlog.Error("Count" + ":" + filter.ToString());
                nlog.Fatal(e);
                throw e;
            }
        }
        public static IQueryable<T> Query<T>()
            where T : MongoDBBaseModel
        {
            try
            {
                var collection = getCollection<T>();
                var result = collection.AsQueryable<T>();
                return result;
            }
            catch (Exception e)
            {
                nlog.Error("Query" + ":" + typeof(T).Name);
                nlog.Fatal(e);
                throw e;
            }
        }
        #endregion
    }

    /// <summary>
    /// MongoDB基础模型类，所有插入MongoDB的类的基类
    /// </summary>
    public class MongoDBBaseModel
    {
        private DateTime addTime = DateTime.Now;
        /// <summary>
        /// MongoDB的主键Id，插入时可为空，MongoDB会自动添加
        /// </summary>
        [JsonIgnore]
        public ObjectId _id { get; set; }
        /// <summary>
        /// 数据库添加时间，插入时可为空，默认当前时间
        /// </summary>
        public DateTime AddTime
        {
            get
            {
                return addTime;
            }
            set
            {
                addTime = value;
            }
        }
    }
}
